package client_core

import (
	"context"
	"fmt"
	"github.com/chicken-team-outside/chicken_transmission/api_messages"
	"github.com/chicken-team-outside/chicken_transmission/client_core/service"
	"github.com/chicken-team-outside/chicken_transmission/model"
	"github.com/chicken-team-outside/chicken_transmission/sfs"
	"github.com/chicken-team-outside/chicken_transmission/utils"
)

func walkDeviceDirRecursive(ctx context.Context, deviceId uint64, deviceDir, localDir string, callback func(deviceParent, localParent string, f []sfs.FileInfo) error) error {
	client := GetSfsClient()
	if client == nil {
		return ErrSFSClientNotAvailable
	}
	files, err := client.ListDir(ctx, deviceId, &api_messages.ListFilesQuery{Path: deviceDir})
	if err != nil {
		return err
	}
	err = callback(deviceDir, localDir, files)
	if err != nil {
		return err
	}
	for i := 0; i < len(files) && ctx.Err() == nil; i++ {
		if files[i].IsDir() {
			err = walkDeviceDirRecursive(ctx, deviceId, utils.ConcatPath(deviceDir, files[i].Name()), utils.ConcatPath(localDir, files[i].Name()), callback)
			if err != nil {
				return err
			}
		}
	}
	return ctx.Err()
}

func (m *TaskRunnerMgr) convertAndAddDownloadTasks(task *TaskWarp, deviceParent, localParent string, f []sfs.FileInfo) error {
	m.dirRunner.taskLock.Lock()
	defer m.dirRunner.taskLock.Unlock()
	var tasks []*model.Task
	for i := 0; i < len(f); i++ {
		if !f[i].IsDir() {
			t := &model.Task{
				Total:      f[i].Size(),
				DeviceId:   task.info.DeviceId,
				DevicePath: utils.ConcatPath(deviceParent, f[i].Name()),
				LocalPath:  utils.ConcatPath(localParent, f[i].Name()),
			}
			if task.info.PausedSubTask {
				t.Status = model.TaskStatus_Paused
			} else {
				t.Status = model.TaskStatus_Waiting
			}
			t.ParentId = task.info.Id
			t.TaskType = model.TaskType_DownloadFile
			if ok, err := service.ExistsSameTask(t); err != nil {
				return err
			} else if ok {
				continue
			}
			tasks = append(tasks, t)
			task.AddTotal(1)
		}
	}
	return m.fileRunner.AddTasks(tasks)
}

func (m *TaskRunnerMgr) runDownloadDirScanTask(task *TaskWarp) error {
	return walkDeviceDirRecursive(task.ctx, task.info.DeviceId, task.info.DevicePath, task.info.LocalPath, func(deviceParent, localParent string, f []sfs.FileInfo) error {
		return m.convertAndAddDownloadTasks(task, deviceParent, localParent, f)
	})
}

func (m *TaskRunnerMgr) opDownloadDirScanTask(ids []int64, pausedSubTask bool, resolveRunning func([]int64) error) error {
	return m.dirRunner.opWithLock(true, func() error {
		m.fileRunner.taskLock.Lock()
		defer m.fileRunner.taskLock.Unlock()
		err := service.UpdateTaskPausedSubTask(model.TaskType_DownloadDir, ids, pausedSubTask)
		if err != nil {
			return fmt.Errorf("failed to UpdateTaskPausedSubTask: %w", err)
		}
		for _, id := range ids {
			if warp, ok := m.fileRunner.tasks[id]; ok {
				warp.taskLock.Lock()
				if warp.info.PausedSubTask != pausedSubTask {
					warp.info.PausedSubTask = pausedSubTask
				}
				warp.taskLock.Unlock()
			}
		}
		ids, err = service.FindAllTaskIdsByParentIds(model.TaskType_DownloadFile, ids)
		if err != nil {
			return fmt.Errorf("failed to FindAllTaskIdsByParentIds: %w", err)
		}
		if len(ids) > 0 {
			err = resolveRunning(ids)
		}
		return nil
	})
}

func (m *TaskRunnerMgr) resumeDownloadDirTask(ids []int64) error {
	return m.opDownloadDirScanTask(ids, false, m.resumeDownloadTaskCb)
}

func (m *TaskRunnerMgr) pauseDownloadDirTask(ids []int64) error {
	return m.opDownloadDirScanTask(ids, true, m.pauseDownloadTaskCb)
}

func (m *TaskRunnerMgr) cancelDownloadDirTask(ids []int64, f func() error) error {
	return m.dirRunner.opWithLock(true, func() (err error) {
		ids, err = service.FindTasksByStatus(model.TaskType_DownloadDir, canPauseStatus, ids)
		if len(ids) > 0 {
			m.dirRunner.taskLock.Lock()
			defer m.dirRunner.taskLock.Unlock()
			var waiting []int64
			for _, id := range ids {
				if warp, ok := m.dirRunner.tasks[id]; ok {
					warp.stop()
				} else {
					waiting = append(waiting, id)
				}
			}
			if len(waiting) > 0 {
				err = service.UpdateTasksStatus(model.TaskType_DownloadDir, canPauseStatus, model.TaskStatus_Paused, "", waiting)
			}
		}
		if err == nil {
			err = f()
		}
		return
	})
}

func (m *TaskRunnerMgr) deleteDownloadDirTask(ids []int64) (err1 error) {
	return m.cancelDownloadDirTask(ids, func() (err error) {
		ids, err = service.FindTasksByStatus(model.TaskType_DownloadDir, canDeleteStatus, ids)
		if len(ids) > 0 {
			ids1 := ids
			ids, err = service.FindAllTaskIdsByParentIds(model.TaskType_DownloadFile, ids)
			if err == nil {
				err = service.DeleteTaskByIds(model.TaskType_DownloadDir, ids1)
			}
			if err == nil {
				sendTaskStatusChangeByIds(model.TaskType_DownloadDir, TaskMgrOpTypeDeleteTask, ids1)
			}
		}
		if len(ids) > 0 {
			err = m.deleteDownloadTaskCb(ids)
		}
		return
	})
}
